﻿using System;
using System.Collections.Generic;
using System.Common.Native.Data;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace System.Common.Native.Hooks
{
  public abstract class SystemHook<T> : IDisposable
    where T : struct
  {
    public delegate void UserHookEventHandler( IntPtr wParam, T lParam );

    private readonly object fLckObj = new object();
    private readonly HookProc fSystemHookProc;

    private IntPtr fHookId = IntPtr.Zero;

    protected SystemHook()
    {
      fSystemHookProc = new HookProc( SystemHookProc );
    }

    ~SystemHook()
    {
      Dispose();
    }

    public abstract HookType HookType { get; }
    internal abstract HookParamType Paramtype { get; }

    protected bool IsActive
    {
      get
      {
        lock ( fLckObj )
          return fHookId != IntPtr.Zero;
      }
    }

    private int SystemHookProc( int nCode, IntPtr wParam, IntPtr lParam )
    {
      if ( nCode >= 0 )
      {
        Console.WriteLine( wParam.ToInt32() );
        OnUserHook( wParam, lParam );
      }

      return UserHooks.CallNextHookEx( fHookId, nCode, wParam, lParam );

    }

    #region UserHookEvent

    private WeakEvent<IntPtr, T> __weUserHookEventHandler = new WeakEvent<IntPtr, T>();
    public event UserHookEventHandler UserHook
    {
      add
      {
        if ( !IsActive )
          CreateHook();

        __weUserHookEventHandler.Add( value );
      }
      remove
      {
        __weUserHookEventHandler.Remove( value );

        if ( __weUserHookEventHandler.GetInvocationCount()==0 )
          DestroyHook();
      }
    }

    private void OnUserHook( IntPtr wParam, IntPtr lParam )
    {
      T lpStruct = new T();

      switch ( Paramtype )
      {
      case HookParamType.ConvertToStruct:
        lpStruct = (T)Marshal.PtrToStructure( lParam, typeof( T ) );
        __weUserHookEventHandler.Invoke( wParam, lpStruct );
        break;

      case HookParamType.NonConvertible:
        __weUserHookEventHandler.Invoke( wParam, lpStruct );
        break;
      }
    }

    #endregion

    #region Create & Destroy hook

    private void CreateHook()
    {

      lock ( fLckObj )
      {
        if ( fHookId == IntPtr.Zero )
        {
          using ( var curProcess = Process.GetCurrentProcess() )
          {
            using ( var curModule = curProcess.MainModule )
              fHookId = UserHooks.SetWindowsHookEx( (int)HookType, fSystemHookProc,
                                                Kernel.GetModuleHandle( curModule.ModuleName ), 0 );
          }
        }
      }

    }

    private void DestroyHook()
    {
      lock ( fLckObj )
      {
        if ( fHookId != IntPtr.Zero )
        {
          UserHooks.UnhookWindowsHookEx( fHookId );
          fHookId = IntPtr.Zero;
        }
      }
    }

    #endregion

    #region IDisposable Members

    public virtual void Dispose()
    {
      DestroyHook();
    }

    #endregion

  }
}
